// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Text;
using SnapObjects.Commons;

namespace SnapObjects.Data
{
    public abstract class SqlGenerationHelper : ISqlGenerationHelper
    {
        /// <summary>
        ///     Initializes a new instance of the this class.
        /// </summary>
        /// <param name="options"> Parameter object containing options for this service. </param>
        public SqlGenerationHelper(IDataContextOptions options)
        {
            this.ContextOptions = options;
        }

        protected IDataContextOptions ContextOptions { get; }

        public abstract char ParameterIdentifier { get; }

        public virtual string OutputParameterPlacehold => "_Output_xxx_xxxx_xxx_123_";

        /// <summary>
        ///     The terminator to be used for SQL statements.
        /// </summary>
        public virtual string StatementTerminator => ";";

        /// <summary>
        ///     The terminator to be used for batches of SQL statements.
        /// </summary>
        public virtual string BatchTerminator => String.Empty;

        /// <summary>
        ///     Generates a valid parameter name for the given candidate name.
        /// </summary>
        /// <param name="name">The candidate name for the parameter.</param>
        /// <returns>
        ///     A valid name based on the candidate name.
        /// </returns>
        public virtual string GenerateParameterName(string name)
        {
            return this.ParameterIdentifier + name;
        }

        /// <summary>
        ///     Writes a valid parameter name for the given candidate name.
        /// </summary>
        /// <param name="builder">The <see cref="StringBuilder" /> to write generated string to.</param>
        /// <param name="name">The candidate name for the parameter.</param>
        public virtual void GenerateParameterName(StringBuilder builder, string name)
        {
            builder.Append(this.ParameterIdentifier).Append(name);
        }

        /// <summary>
        ///     Generates the escaped SQL representation of a literal value.
        /// </summary>
        /// <param name="literal">The value to be escaped.</param>
        /// <returns>
        ///     The generated string.
        /// </returns>
        public virtual string EscapeLiteral(string literal)
        {
            return Check.NotNull(literal, nameof(literal)).Replace("'", "''");
        }

        /// <summary>
        ///     Writes the escaped SQL representation of a literal value.
        /// </summary>
        /// <param name="builder">The <see cref="StringBuilder" /> to write generated string to.</param>
        /// <param name="literal">The value to be escaped.</param>
        public virtual void EscapeLiteral(StringBuilder builder, string literal)
        {
            Check.NotNull(literal, nameof(literal));

            var initialLength = builder.Length;
            builder.Append(literal);
            builder.Replace("'", "''", initialLength, literal.Length);
        }

        /// <summary>
        ///     Generates the escaped SQL representation of an identifier (column name, table name, etc.).
        /// </summary>
        /// <param name="identifier">The identifier to be escaped.</param>
        /// <returns>
        ///     The generated string.
        /// </returns>
        public virtual string EscapeIdentifier(string identifier)
        {
            return Check.NotEmpty(identifier, nameof(identifier))
                .Replace(this.RightDelimitIdentifier, this.RightDelimitIdentifier + this.RightDelimitIdentifier);
        }

        /// <summary>
        ///     Writes the escaped SQL representation of an identifier (column name, table name, etc.).
        /// </summary>
        /// <param name="builder">The <see cref="StringBuilder" /> to write generated string to.</param>
        /// <param name="identifier">The identifier to be escaped.</param>
        public virtual void EscapeIdentifier(StringBuilder builder, string identifier)
        {
            Check.NotEmpty(identifier, nameof(identifier));

            var initialLength = builder.Length;

            builder.Append(identifier);

            builder.Replace(this.RightDelimitIdentifier,
                this.RightDelimitIdentifier + this.RightDelimitIdentifier,
                initialLength,
                identifier.Length);
        }

        /// <summary>
        ///     Generates the delimited SQL representation of an identifier (column name, table name, etc.).
        /// </summary>
        /// <param name="identifier">The identifier to delimit.</param>
        /// <returns>
        ///     The generated string.
        /// </returns>
        public virtual string DelimitIdentifier(string identifier)
        {
            if (this.ContextOptions.EnableDelimitIdentifier)
            {
                return $"{this.LeftDelimitIdentifier}" +
                    $"{this.EscapeIdentifier(Check.NotEmpty(identifier, nameof(identifier)))}" +
                    $"{this.RightDelimitIdentifier}"; // Interpolation okay; strings
            }

            return identifier;
        }

        /// <summary>
        ///     Writes the delimited SQL representation of an identifier (column name, table name, etc.).
        /// </summary>
        /// <param name="builder">The <see cref="StringBuilder" /> to write generated string to.</param>
        /// <param name="identifier">The identifier to delimit.</param>
        public virtual void DelimitIdentifier(StringBuilder builder, string identifier)
        {
            Check.NotEmpty(identifier, nameof(identifier));

            if (this.ContextOptions.EnableDelimitIdentifier)
            {
                builder.Append(this.LeftDelimitIdentifier);
                this.EscapeIdentifier(builder, identifier);
                builder.Append(this.RightDelimitIdentifier);
            }
            else
            {
                builder.Append(identifier);
            }
        }

        /// <summary>
        ///     Generates the delimited SQL representation of an identifier (column name, table name, etc.).
        /// </summary>
        /// <param name="name">The identifier to delimit.</param>
        /// <param name="schema">The schema of the identifier.</param>
        /// <returns>
        ///     The generated string.
        /// </returns>
        public virtual string DelimitIdentifier(string name, string schema)
        {
            return (!String.IsNullOrEmpty(schema)
                              ? this.DelimitIdentifier(schema) + "."
                              : String.Empty)
                          + this.DelimitIdentifier(Check.NotEmpty(name, nameof(name)));
        }

        /// <summary>
        ///     Writes the delimited SQL representation of an identifier (column name, table name, etc.).
        /// </summary>
        /// <param name="builder">The <see cref="StringBuilder" /> to write generated string to.</param>
        /// <param name="name">The identifier to delimit.</param>
        /// <param name="schema">The schema of the identifier.</param>
        public virtual void DelimitIdentifier(StringBuilder builder, string name, string schema)
        {
            if (!String.IsNullOrEmpty(schema))
            {
                this.DelimitIdentifier(builder, schema);
                builder.Append(".");
            }

            this.DelimitIdentifier(builder, name);
        }

        public virtual bool IsParameterPlaceholder(char c)
        {
            return c == this.ParameterIdentifier;
        }

        public virtual string SqlBegin => String.Empty;
        public virtual string SqlEnd => String.Empty;

        public virtual bool IsDelimitIdentifier(string left, string right)
        {
            return this.LeftDelimitIdentifier.Equals(left) && this.RightDelimitIdentifier.Equals(right);
        }

        public virtual string RemoveDelimitIdentifier(string objectName)
        {
            if (this.TryRemoveDelimitIdentifier(
                objectName, this.LeftDelimitIdentifier, this.RightDelimitIdentifier, out var name))
            {
                return name;
            }

            return objectName;
        }

        protected bool TryRemoveDelimitIdentifier(
            string objectName, string leftDelimit, string rigthDelimit, out string name)
        {
            name = objectName;

            if (String.IsNullOrWhiteSpace(name))
            {
                return true;
            }

            name = name.Trim();

            if (name.StartsWith(leftDelimit) && name.EndsWith(rigthDelimit))
            {
                name = name[leftDelimit.Length..];
                name = name.Remove(name.Length - rigthDelimit.Length);

                return true;
            }

            return false;
        }

        protected virtual string LeftDelimitIdentifier => "\"";

        protected virtual string RightDelimitIdentifier => "\"";
    }
}
